Initial commit of prototype converter for Rohde and Schwarz IQ.TAR #144
Initial commit of prototype converter for Rohde and Schwarz IQ.TAR #144KelseyCreekSoftware wants to merge 6 commits into
Conversation
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 17 high |
🟢 Metrics 90 complexity · 5 duplication
Metric Results Complexity 90 Duplication 5
TIP This summary will be updated as you push new changes. Give us feedback
|
Before we get going on this Rohde & Swarz support, is pr #136 ready to merge? If so let's do that and then you can rebase this branch. |
|
Sure, that sounds like a good plan. |
|
I merged the Signal Hound branch so you should be able to rebase this branch to main now. |
…les. See TODOs and project page for more detail.
c711b3b to
09fd807
Compare
| from . import detect_converter | ||
| from .blue import blue_to_sigmf | ||
| from .signalhound import signalhound_to_sigmf | ||
| from .signalhound import signalhound_to_sigmf |
There was a problem hiding this comment.
Pull request overview
Adds an initial Rohde & Schwarz IQ.TAR → SigMF conversion path to the sigmf.convert subsystem, wiring it into converter auto-detection and the sigmf_convert CLI, and introducing tests to validate key behaviors.
Changes:
- Introduce
sigmf/convert/rohdeschwarz.pyimplementing IQ.TAR extraction, XML parsing/validation, SigMF metadata construction, and dataset/archive/NCD writing. - Extend converter detection + CLI dispatch to recognize
.tarIQ.TAR recordings and invoke the new converter. - Add unit tests covering XML-to-dict conversion, TAR extraction, validation failures, metadata building, IQ reading, and NCD creation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 15 comments.
| File | Description |
|---|---|
sigmf/convert/rohdeschwarz.py |
New Rohde & Schwarz IQ.TAR converter implementation (extraction, validation, metadata + data conversion, output writing). |
sigmf/convert/__init__.py |
Extend converter detection to recognize Rohde & Schwarz IQ.TAR and enhance magic-byte lookup. |
sigmf/convert/__main__.py |
Wire the new converter into the unified conversion CLI. |
tests/test_convert_rohdeschwarz.py |
New test suite for the Rohde & Schwarz converter. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| member_path = os.path.join(target_dir, member.name) | ||
| abs_target = os.path.abspath(target_dir) | ||
| abs_member = os.path.abspath(member_path) | ||
|
|
||
| return abs_member.startswith(abs_target) |
| def safe_extract(tar, target_dir): | ||
| """ | ||
| Extract only safe members from a tarfile. | ||
| """ | ||
| for member in tar.getmembers(): | ||
| if not is_safe_member(tar, member, target_dir): | ||
| raise Exception(f"Unsafe path detected in TAR: {member.name}") | ||
| tar.extract(member, target_dir) | ||
|
|
| # validate CenterFrequency | ||
| center_freq_raw = _text_of(root, "Clock") | ||
| try: | ||
| center_frequency = float(center_freq_raw) | ||
| except (TypeError, ValueError) as err: | ||
| raise SigMFConversionError(f"Invalid or missing CenterFrequency: {center_freq_raw}") from err | ||
|
|
||
| # validate SampleRate | ||
| num_samples_raw = _text_of(root, "Samples") | ||
| try: | ||
| sample_rate = float(num_samples_raw) | ||
| except (TypeError, ValueError) as err: | ||
| raise SigMFConversionError(f"Invalid or missing SampleRate: {num_samples_raw}") from err | ||
|
|
||
| if sample_rate <= 0: | ||
| raise SigMFConversionError(f"Invalid SampleRate: {sample_rate} (must be > 0)") | ||
|
|
| # add optional rohdeschwarz-specific fields to global metadata using rohdeschwarz: namespace | ||
| # only include fields that aren't already represented in standard SigMF metadata | ||
| if scaling_factor: | ||
| global_md["rohdeschwarz:scaling_factor"] = scaling_factor | ||
| if datafilename: | ||
| global_md["rohdeschwarz:iq_datafilename"] = datafilename # provenance | ||
| if userdata: | ||
| global_md["rohdeschwarz:userdata"] = userdata #open ended field for user defined data. | ||
| if preview_data: | ||
| global_md["rohdeschwarz:preview_trace"] = preview_data |
| # Get unique IQ filename from global_info | ||
| iq_filename = global_info.get("rohdeschwarz:iq_datafilename") | ||
| print(f"iq_filename: {iq_filename}") |
| elif file_path.suffix in [".tar"]: | ||
| # iq.tar file extensions are used by Rohde & Schwarz for their IQ data, but the .tar extension is also used by other formats. | ||
| # So parse the tar file to determine if it is a Rohde & Schwarz file or not. | ||
| rohde_schwarz_expanded_magic_bytes = get_magic_bytes(file_path, count=20, offset=0,magic_bytes=b"RS_IQ_TAR_FileFormat") # <RS_IQ_TAR_FileFormat> | ||
| if rohde_schwarz_expanded_magic_bytes == b"RS_IQ_TAR_FileFormat": | ||
| return "rohdeschwarz" | ||
| else: | ||
| raise SigMFConversionError( | ||
| f"Unsupported XML file format. Root element: {rohde_schwarz_expanded_magic_bytes}. " | ||
| f"Expected RS_IQ_TAR_FileFormat for IQ.TAR files." | ||
| ) | ||
|
|
| """ | ||
| Create a minimal, valid Rohde & Schwarz IQ.TAR file for testing. | ||
| """ |
| data_file_path = rohdeschwarz_path.parent / iq_filename | ||
| print(f"data_file_path: {data_file_path}") |
| output_dir = filenames["archive_fn"].parent | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
| meta.tofile(filenames["archive_fn"], toarchive=True) | ||
| log.info("wrote SigMF archive to %s", filenames["archive_fn"]) |
| # write metadata file | ||
| meta.tofile(filenames["meta_fn"], toarchive=False) | ||
| log.info("wrote SigMF metadata to %s", filenames["meta_fn"]) |
See TODOs and project page for more detail.
https://github.com/KelseyCreekSoftware/KelseyCreekSoftware/blob/main/Rohde-Schwarz-Project-Notes.md